package org.erikaredmark.monkeyshines.editor; import java.util.HashMap; import java.util.List; import java.util.Map; import org.erikaredmark.monkeyshines.Conveyer; import org.erikaredmark.monkeyshines.Goodie; import org.erikaredmark.monkeyshines.Hazard; import org.erikaredmark.monkeyshines.ImmutablePoint2D; import org.erikaredmark.monkeyshines.LevelScreen; import org.erikaredmark.monkeyshines.Sprite; import org.erikaredmark.monkeyshines.World; import org.erikaredmark.monkeyshines.WorldCoordinate; import org.erikaredmark.monkeyshines.encoder.EncodedWorld; import org.erikaredmark.monkeyshines.resource.WorldResource; import com.google.common.collect.ImmutableList; /** * * Wraps a world and provides editor functions as well as the ability to save the world to a file. Intended ONLY for * the level editor. * * @author Erika Redmark * */ public final class WorldEditor { private final World world; private final WorldResource rsrc; private Map<Integer, LevelScreenEditor> levelScreenEditors = new HashMap<Integer, LevelScreenEditor>(); private WorldEditor(final World world, final WorldResource rsrc) { this.world = world; this.rsrc = rsrc; } public static WorldEditor newWorld(final String name, final WorldResource rsrc) { return new WorldEditor(World.newWorld(name, rsrc), rsrc); } public static WorldEditor fromExisting(final World world, WorldResource rsrc) { return new WorldEditor(world, rsrc); } /** * * Creates a world editor based on a world already existing, and inflates the world with the given graphics resource pack * and assigns it to a new level editor instance * * @param encoded * the encoded world file * * @param rsrc * the graphics resource * * @return * a world editor for editing the world * */ public static WorldEditor fromEncoded(EncodedWorld encoded, WorldResource rsrc) { World world = encoded.newWorldInstance(rsrc); // Make all existing sprites visible for (LevelScreen lvl : world.getLevelScreens().values() ) { for (Sprite s : lvl.getSpritesOnScreen() ) { s.setVisible(true); } } return new WorldEditor(world, rsrc); } /** * * Returns the world resource this editor currently is using for the world. * * @return * */ public WorldResource getWorldResource() { return this.rsrc; } /** * Returns the level screen editor for the given screen id. Screen editors are persisted in the world editor, so once * initialised with the world's screen, all changes are saved as long as the reference to the world editor is kept. * <p/> * This method does NOT change the current screen displayed. That should be done with {@link #changeCurrentScreen(int)} * * @param id * the id of the screen * * @return * either a newly initialised, or pre-existing level screen editor for the given id. If the screen does not exist, * a new one will be created */ public LevelScreenEditor getLevelScreenEditor(final int id) { LevelScreenEditor ed = levelScreenEditors.get(id); if (ed != null) return ed; // Create a new screen editor from an existing screen in the world. if (world.screenIdExists(id) ) { return LevelScreenEditor.from(world.getScreenByID(id) ); } // Create a new, empty screen for the level LevelScreen newScreen = LevelScreen.newScreen(id, getWorldResource() ); world.addScreen(newScreen); return LevelScreenEditor.from(newScreen); } /** * Changes the world editor to display the screen for the current level screen editor. A valid editor instance must * be held (get from {@link #getLevelScreenEditor(int)}) to insure that the screen actually exists before displaying. * * @param editor * the level screen editor to mark as current. This editor will be displayed on the main screen i.e the game * engine will be running this level */ public void changeCurrentScreen(LevelScreenEditor editor) { boolean couldChange = world.changeCurrentScreen(editor.getId(), null); if (!(couldChange) ) { System.err.println("Could not change screen to " + editor.getId() ); } } /** * * Copies the level at {@code copyFromId} to the level at {@code copyToId}, either creating a new level if one does not * exist or overwriting an existing one. * * @param copyFromId * * @param copyToId * * @throws IllegalArgumentException * if {@code copyFromId} does not refer to a valid screen * */ public void copyAndPasteLevel(int copyFromId, int copyToId) { if (!(screenExists(copyFromId) ) ) { throw new IllegalArgumentException("Screen id " + copyFromId + " does not exist for copy/paste"); } LevelScreen newScreen = LevelScreen.copyAndAddToWorld(world.getScreenByID(copyFromId), copyToId, world); // Screen already added; just need to create an editor for it levelScreenEditors.put(newScreen.getId(), LevelScreenEditor.from(newScreen) ); } /** * Forwarding call to {@link World#addGoodie(int, int, int, int) } * * @param screenId * id of screen goodie will appear on * * @param i * row of goodie * * @param j * column of goodie * * @param goodieId * id of the actual goodie (apple, orange, gray key, etc...) */ public void addGoodie(int screenId, int i, int j, Goodie.Type goodieType) { world.addGoodie(screenId, i, j, goodieType); } /** * Forwarding call to {@link World#removeGoodie(int, int, int) } */ public void removeGoodie(int screenId, int row, int col) { world.removeGoodie(screenId, row, col); } /** * Forwarding call to {@link World#getHazards() } */ public List<Hazard> getHazards() { return world.getHazards(); } /** * Forwarding call to {@link World#setHazards() } */ public void setHazards(ImmutableList<Hazard> hazards) { world.setHazards(hazards); } /** * Forwards call to {@link World$getConveyers() } * Conveyer belts are auto-generated by the world based on the graphics resource, * hence there is no explicit set command. */ public List<Conveyer> getConveyers() { return world.getConveyers(); } /** * * Sets bonzo to be starting at the given x/y tile location on the screen enumerated by the given id. These co-ordindates * refer to tiles, not pixels. Bonzo is a 2 by 2 tile size, and this location is the upper-left location he will start. * * @param x * bonzo x starting location, tile number * * @param y * bonzo y starting location, number * * @param id * id of the screen. This screen must already exist * * @throws * IllegalArgumentException * if no screen by the given id exists * */ public void setBonzo(int xTile, int yTile, int id) { if (world.screenIdExists(id) == false) throw new IllegalArgumentException("Screen id " + id + " does not exist"); world.getScreenByID(id).setBonzoStartingLocation(ImmutablePoint2D.of(xTile, yTile) ); } /** Forwarding call to {@link World#getWorldName() } */ public String getWorldName() { return world.getWorldName(); } /** * Forwarding call to {@link World#getGoodies() } * <p/> * Additionally, because the world is wrapped in the editor, goodies won't be deleted unless requested by editor, * the returned map represents the static location of every goodie on the map. * * @return * immutable map of all goodies */ public Map<WorldCoordinate, Goodie> getGoodies() { return world.getGoodies(); } /** Forwarding call to {@link World#getLevelScreens() } * Unless encoding, clients should stick with LevelScreenEditor objects. */ public Map<Integer, LevelScreen> getLevelScreens() { return world.getLevelScreens(); } /** * * @param id * * @return * {@code true} if the screen by the given id already exists, {@code false} if otherwise * */ public boolean screenExists(int id) { return world.screenIdExists(id); } /** * * Returns the underlying world being edited * * @return * world being edited * */ public World getWorld() { return this.world; } }